package org.tool.reflect;

import cn.hutool.core.convert.Convert;
import org.tool.TipException;
import org.tool.core.lang.Assert;

import java.beans.FeatureDescriptor;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.*;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * 标准 Java Bean 相关工具类
 *
 * @author 李坤
 * @date 2022/4/22 16:42
 */
public abstract class JavaBeanUtil {

    /**
     * 属性名称 到 字段 的映射容器
     */
    private static final Map<Class, Map<String, PropertyDescriptor>> name2PropertyDescriptorContainer = new HashMap<>();

    /**
     * 获取给定类型-{@code beanClass}可被识别的字段
     *
     * @param beanClass 待处理的类型
     * @return 可被识别的字段
     */
    public static Set<String> getFields(Class<?> beanClass) {
        return Arrays.stream(getPropertyDescriptors(beanClass)).map(FeatureDescriptor::getName).collect(Collectors.toSet());
    }

    /**
     * 获取给定的对象-javaBean的对应属性-propertyName的对应的getter/is方法的值
     *
     * @param propertyName 属性名称
     * @param javaBean     java bean 对象
     * @return 对应的属性值
     */
    public static Object getBeanProperty(String propertyName, Object javaBean) {
        try {
            return getPropertyDescriptor(propertyName, javaBean).getReadMethod().invoke(javaBean);
        } catch (Exception exception) {
            throw new TipException(String.format("执行给定属性 %s 的 getter 方法失败", propertyName));
        }
    }

    /**
     * 设置给定对象-javaBean的对应属性-propertyName的值
     *
     * @param propertyName  属性名称
     * @param propertyValue 属性新值
     * @param javaBean      java bean 对象
     */
    public static void setBeanProperty(String propertyName, Object propertyValue, Object javaBean) {
        final Method writeMethod = getPropertyDescriptor(propertyName, javaBean).getWriteMethod();
        final Class<?> parameterType = writeMethod.getParameterTypes()[0];

        try {
            final Class<?> aClass;
            // 设置值为空 或 类型与所需类型相同 则直接设置；否则尝试进行类型转换
            if (Objects.isNull(propertyValue) ||
                    Objects.equals((aClass = propertyValue.getClass()), parameterType) || aClass.isAssignableFrom(parameterType)) {
                writeMethod.invoke(javaBean, propertyValue);
            } else {
                final Object convert;
                if (Objects.nonNull(convert = Convert.convert(parameterType, propertyValue))) {
                    writeMethod.invoke(javaBean, convert);
                } else {
                    // 可能为基本类型
                    writeMethod.invoke(javaBean, propertyValue);
                }
            }
        } catch (Exception exception) {
            throw new TipException(String.format("执行给定属性 %s 的 setter 方法失败", propertyName));
        }
    }


    /*--------------------------------------------------private methods-----------------------------------------------*/


    /**
     * 获取给定类的属性数据
     *
     * @param beanClass 给定的类
     * @return 对应的属性数据
     * @throws IntrospectionException 内省异常
     */
    private static PropertyDescriptor[] getPropertyDescriptors(Class beanClass) {
        Assert.notNull(beanClass, "给定的 beanClass 不能为空");

        try {
            // 获取给定Class类型给定的属性数据（内部已实现缓存）
            return Introspector.getBeanInfo(beanClass).getPropertyDescriptors();
        } catch (Exception exception) {
            throw new TipException(String.format("类型 %s 内省获取BeanInfo对象失败", beanClass));
        }
    }

    /**
     * 获取给定Java Bean对象和属性名称对应的PropertyDescriptor对象
     *
     * @param propertyName 属性名称
     * @param javaBean     java bean 对象
     * @return PropertyDescriptor对象
     */
    private static PropertyDescriptor getPropertyDescriptor(String propertyName, Object javaBean) {
        Assert.notNull(javaBean, "给定的 Java Bean 不能为空");
        Assert.hasText(propertyName, "给定的 参数名称 不能为空");

        final Class<?> beanClass = javaBean.getClass();

        Map<String, PropertyDescriptor> stringFieldMap;
        if (Objects.isNull(stringFieldMap = name2PropertyDescriptorContainer.get(beanClass))) {
            stringFieldMap = Arrays.stream(getPropertyDescriptors(beanClass)).parallel()
                    .collect(Collectors.toMap(PropertyDescriptor::getName, Function.identity()));

            name2PropertyDescriptorContainer.put(beanClass, stringFieldMap);
        }
        return Optional.ofNullable(stringFieldMap.get(propertyName)).orElseThrow(() ->
                new TipException(String.format("%s 类中未发现给定属性 %s 对应的方法", beanClass, propertyName)));
    }

}
